use fltk::*;
use fltk::{app::*, button::*, frame::*,valuator::*, widget::*};
use std::fs::File;
use std::io::prelude::*;
use std::sync::{Arc, Mutex};
use std::thread;
use std::time::Duration;

#[derive(Debug, Copy, Clone)]
pub enum GuiCommand {
    Repaint,
    Switch,
    Randomize,
    Save(u16),
    Load(u16),
    Wipe,
    None,
}

static ROUND_DURATION: u64 = 200;
static CELL_WIDTH: usize = 100;
static CELL_HEIGHT: usize = 100;
static CELL_PIX: i32 = 4;
static CELL_UPPER_LIMIT: u16 = 3;
static CELL_BE_REBORN: u16 = 3;
static CELL_LOWER_LIMIT: u16 = 2;
static BTN_HEIGHT : i32 = 40;

fn main() {
    //Setup APP and Window
    let app = app::App::default().with_scheme(AppScheme::Gtk);
    let cellarr = vec![vec![false; CELL_WIDTH]; CELL_HEIGHT];
    let wind_width = CELL_WIDTH as i32 * CELL_PIX;
    let wind_height = CELL_HEIGHT as i32 * CELL_PIX;
    let wind_width_buttons = 200;
    let mut wind = window::DoubleWindow::default()
        .with_size(wind_width + wind_width_buttons, wind_height)
        .center_screen()
        .with_label("Game of Life");

    let (s, r) = channel::<GuiCommand>();

    //Arc for controlling the calculation thread (runcondition)
    let condition_r = Arc::new(Mutex::new(false));
    let condition_t = condition_r.clone();

    let duration_s = Arc::new(Mutex::new(ROUND_DURATION));
    let duration_a = duration_s.clone();

    //GUI Elements
    let mut btnpos = 0;
    
    let mut game = Frame::default().with_size(wind_width, wind_height);
    let mut btn_swtich = Button::new(wind_width, btnpos, wind_width_buttons, BTN_HEIGHT, "Start");
    btn_swtich.set_label_font(Font::CourierBold);
    btn_swtich.set_color(Color::Green);
    btn_swtich.visible_focus(false);
    btn_swtich.emit(s, GuiCommand::Switch);
    btnpos += BTN_HEIGHT;
    
    let mut btn_rando = Button::new(wind_width, btnpos, wind_width_buttons, BTN_HEIGHT, "Randomize!");
    btn_rando.visible_focus(false);
    btn_rando.set_label_font(Font::CourierBold);
    btn_rando.emit(s, GuiCommand::Randomize);
    btnpos += BTN_HEIGHT;

    let mut btn = Button::new(wind_width, btnpos, wind_width_buttons, BTN_HEIGHT, "RIP Conway!");
    btn.visible_focus(false);
    btn.set_label_font(Font::CourierBold);
    btn.emit(s, GuiCommand::Load(0));
    btnpos += BTN_HEIGHT;

    let mut btn = Button::new(wind_width, btnpos, wind_width_buttons, BTN_HEIGHT, "Wipe");
    btn.visible_focus(false);
    btn.set_label_font(Font::CourierBold);
    btn.emit(s, GuiCommand::Wipe);
    btnpos += BTN_HEIGHT;

    let mut slide = ValueSlider::new(wind_width, btnpos, wind_width_buttons, BTN_HEIGHT, "Speed");
    slide.visible_focus(false);
    slide.set_label_font(Font::CourierBold);
    slide.set_type(SliderType::Horizontal);
    slide.set_bounds(50f64, 1000f64);
    slide.set_step(50f64,1);
    slide.set_value(ROUND_DURATION as f64);
    //Clone of Widget does not clone the widget only the reference!
    //this is needed for access to the value inside the Callback.
    let c = slide.clone();
    slide.set_callback(Box::new(move || {
        let val = c.value();
        let mut dur = duration_s.lock().expect("duration-lock failed at callback");
        *dur = val as u64;
        drop(dur);
    }));
    

    let slot_btn_width = wind_width_buttons / 2;
    for i in 1u16..6u16 {
        let x = btnpos + i as i32 * BTN_HEIGHT;
        let mut btn_load = Button::new(wind_width, x, slot_btn_width, BTN_HEIGHT, &format!("Load {}", i));
        btn_load.visible_focus(false);
        btn_load.set_label_font(Font::CourierBold);
        btn_load.emit(s, GuiCommand::Load(i));

        let mut btn_save = Button::new(
            wind_width + slot_btn_width,
            x,
            slot_btn_width,
            BTN_HEIGHT,
            &format!("Save {}", i),
        );
        btn_save.visible_focus(false);
        btn_save.set_label_font(Font::CourierBold);
        btn_save.emit(s, GuiCommand::Save(i));
    }

    //Arc for use in the calulation thread
    let calcarr = Arc::new(Mutex::new(cellarr));
    //Arc for use in App-Thread
    let app_arr = calcarr.clone();

    //Arc use in the GUI Thread(drawing)
    let drawarr = Arc::clone(&calcarr);
    

    //Conway
    load_conway(calcarr.clone());

    //Game redrawing the play area from cell array
    game.draw(Box::new(move || {
        let arr = drawarr.lock().expect("draw-lock failed!");
        draw::set_draw_color(Color::Black);
        draw::draw_rectf(
            0,
            0,
            CELL_PIX * CELL_WIDTH as i32,
            CELL_PIX * CELL_HEIGHT as i32,
        );
        draw::set_draw_color(Color::White);
        for x in (0..CELL_WIDTH).rev() {
            for y in (0..CELL_HEIGHT).rev() {
                if arr[x][y] {
                    draw::draw_rectf(CELL_PIX * x as i32, CELL_PIX * y as i32, CELL_PIX, CELL_PIX);
                }
            }
        }
        drop(arr);
    }));
    //Click reaction = > Cell Reverse Alive-State
    let handle_arr = calcarr.clone();
    let mut topush = false;
    game.handle(Box::new(move |ev: Event| match ev {
        //Saves the first changed state(opposite of before) and uses this on all pix until button is released
        Event::Push => {
            topush = set_cursor_pix_push(handle_arr.clone());
            true
        },
        Event::Released => set_cursor_pix(handle_arr.clone(), topush),
        Event::Drag => set_cursor_pix(handle_arr.clone(), topush),
        _ => false,
    }));

    //makes sure the runcondition of the calculation thread is turned of, when the window is closed
    wind.handle(Box::new(move |ev: Event| match ev {
        Event::Hide => {
            let mut run = condition_r
                .lock()
                .expect("condition-lock failed at window close!");
            *run = false;
            drop(run);
            true
        }
        _ => false,
    }));

    wind.end();
    wind.show();

    //GUI-LOOP
    while app.wait().expect("app crashed") {
        if let Some(cmd) = r.recv() {
            if let GuiCommand::Repaint = cmd {
                game.redraw();
            } else if let GuiCommand::Switch = cmd {
                let mut run = condition_t
                    .lock()
                    .expect("condition-lock failed at app-thread(switch command)!");
                *run = !*run;
                if *run {
                    make_game_thread(condition_t.clone(),duration_a.clone(), calcarr.clone(), s);
                    btn_swtich.set_label("Stop");
                    btn_swtich.set_color(Color::Red);
                } else {
                    btn_swtich.set_label("Start");
                    btn_swtich.set_color(Color::Green);
                }
                drop(run);
        //Any action that changes the Cell-Vector needs to call a repaint after!
            } else if let GuiCommand::Randomize = cmd {
                let mut cell_r = app_arr.lock().expect("lock failed at randomize!");
                for x in (0..CELL_WIDTH).rev() {
                    for y in (0..CELL_HEIGHT).rev() {
                        cell_r[x][y] = rand::random::<bool>();
                    }
                }
                drop(cell_r);
                s.send(GuiCommand::Repaint);
            } else if let GuiCommand::Save(c) = cmd {
                save(app_arr.clone(), c)
            } else if let GuiCommand::Load(c) = cmd {
                load(app_arr.clone(), c);
                s.send(GuiCommand::Repaint);
            } else if let GuiCommand::Wipe = cmd {
                wipe(app_arr.clone());
                s.send(GuiCommand::Repaint);
            }
        }
    }
}

/// warp will return a corrected version of i.
/// return value will allways be between min and max.format!
/// should i not be within these limits it will be warped to the other end.
fn warp(i: i16, max: usize, min: usize) -> usize {
    if i >= max as i16 {
        return (i - max as i16) as usize;
    } else if i < min as i16 {
        return (i + max as i16) as usize;
    }
    return i as usize;
}

///Wipes the Cell-Vector clean
fn wipe(larr: Arc<Mutex<Vec<Vec<bool>>>>) {
    let mut cell_r = larr.lock().expect("lock failed at wipe!");
    for x in (0..CELL_WIDTH).rev() {
        for y in (0..CELL_HEIGHT).rev() {
            cell_r[x][y] = false;
        }
    }
}

///Loads a file by slot number.
fn load(larr: Arc<Mutex<Vec<Vec<bool>>>>, slot: u16) {
    if slot == 0 {
        load_conway(larr);
    } else {
        let fname = format!("{}.dat", slot);
        let fp = std::path::Path::new(&fname);
        let mut f: File;
        let mut data = vec![0; (CELL_WIDTH * CELL_HEIGHT) / 8 + 1];
        if fp.exists() {
            f = File::open(fp).expect(&format!("failed to open load file {}!", fname));
            f.read(&mut data)
                .expect(&format!("failed to read load file {}!", fname));
            drop(f);
        }
        let mut bcnt: i8 = 7;
        let mut cnt: usize = 0;
        let mut arr = larr.lock().expect("laod-lock failed!");
        for x in 0..CELL_WIDTH {
            for y in 0..CELL_HEIGHT {
                let byte = data[cnt];
                arr[x][y] = if (byte >> bcnt) & 1u8 == 1 {
                    true
                } else {
                    false
                };
                bcnt -= 1;
                if bcnt < 0 {
                    bcnt = 7;
                    cnt += 1;
                }
            }
        }
    }
}

/// Saves the CELL-Vector to a File corresponding with slot.
fn save(larr: Arc<Mutex<Vec<Vec<bool>>>>, slot: u16) {
    let fname = format!("{}.dat", slot);
    let fp = std::path::Path::new(&fname);
    let mut f: File;
    let arr = larr.lock().expect("save-lock failed!");
    //Calc byte count + 1 to avoid cutoff
    let mut data = vec![0; (CELL_WIDTH * CELL_HEIGHT) / 8 + 1];
    let mut byte: u8 = 0;
    let mut bcnt: i8 = 7;
    let mut cnt: usize = 0;
    for x in 0..CELL_WIDTH {
        for y in 0..CELL_HEIGHT {
            //Shift bool into byte, to reduce save-file size from 10 kb to 1,3 kb
            byte = byte | (if arr[x][y] { 1u8 } else { 0u8 } << bcnt);
            bcnt -= 1;
            //If the byte is full write to data
            if bcnt < 0 {
                data[cnt] = byte;
                cnt += 1;
                bcnt = 7;
                byte = 0;
            }
        }
    }
    //If cell amount is not dividable by 8(byte) indicated by bcnt not being below zero, the rest needs to be written.
    if bcnt >= 0 {
        data[cnt] = byte;
    }

    f = File::create(fp).expect(&format!("Creating save file {} failed", fname));
    f.write(&data)
        .expect(&format!("Writing save file {} failed", fname));
    f.sync_all()
        .expect(&format!("Sync save file {} failed", fname));
    drop(f);
    drop(arr);
}

/// To honor the original inventor of the Game of Life John Conway
/// this litte starting pattern was inspired by https://xkcd.com/2293/
/// licensed unter creative commons 2.5, but would be fair use even if it was not.
fn load_conway(larr: Arc<Mutex<Vec<Vec<bool>>>>) {
    let arr = vec![
        (48, 46),
        (49, 46),
        (50, 46),
        (48, 47),
        (50, 47),
        (48, 48),
        (50, 48),
        (49, 49),
        (46, 50),
        (48, 50),
        (49, 50),
        (50, 50),
        (49, 51),
        (49, 52),
        (47, 51),
        (49, 51),
        (51, 51),
        (49, 52),
        (52, 52),
        (48, 53),
        (50, 53),
        (48, 54),
        (50, 54),
    ];
    wipe(larr.clone());
    let mut carr = larr.lock().expect("Lock failed at load_conway");
    for (x, y) in arr {
        carr[x][y] = true;
    }
    drop(carr);
}

///Setus the Pix underneath the cursor to the $state provided.format!
/// return false if cursor is outside bounds, otherwise true.
fn set_cursor_pix(arr: Arc<Mutex<Vec<Vec<bool>>>>, state: bool) -> bool {
    let x = (event_x() / CELL_PIX) as usize;
    let y = (event_y() / CELL_PIX) as usize;
    let mut arr = arr.lock().expect("Lock failed at set_cursor_pix");
    if x >= CELL_WIDTH || y >= CELL_HEIGHT {
        return false;
    }
    arr[x][y] = state;
    if state {
        draw::set_draw_color(Color::White);
    } else {
        draw::set_draw_color(Color::Black);
    }
    draw::draw_rectf(CELL_PIX * x as i32, CELL_PIX * y as i32, CELL_PIX, CELL_PIX);
    drop(arr);
    return true;
}

///Sets the Pix underneath the cursor to the opisite of its current state
/// return the new state or false if cursor is outside bounds.
fn set_cursor_pix_push(arr: Arc<Mutex<Vec<Vec<bool>>>>) -> bool {
    let x = (event_x() / CELL_PIX) as usize;
    let y = (event_y() / CELL_PIX) as usize;
    let mut arr = arr.lock().expect("Lock failed at set_cursor_pix_push");
    let old = arr[x][y];
    if x >= CELL_WIDTH || y >= CELL_HEIGHT {
        return false;
    }
    arr[x][y] = !old;
    if old {
        draw::set_draw_color(Color::Black);
    } else {
        draw::set_draw_color(Color::White);
    }
    draw::draw_rectf(CELL_PIX * x as i32, CELL_PIX * y as i32, CELL_PIX, CELL_PIX);
    drop(arr);
    return !old;
}

///Creates the calculation-thread and returns its handle
fn make_game_thread(
    condition: Arc<Mutex<bool>>,
    duration: Arc<Mutex<u64>>,
    table: Arc<Mutex<Vec<Vec<bool>>>>,
    s: Sender<GuiCommand>,
) -> std::thread::JoinHandle<()> {
    return thread::spawn(move || loop {
        let run = condition
            .lock()
            .expect("condition-lock failed at calculation thread");
        if !*run{
            drop(run);
            break;
        }
        drop(run);

        let dur = duration
            .lock()
            .expect("duration-lock failed at calculation thread");
        thread::sleep(Duration::from_millis(*dur));
        drop(dur);
        let mut arr = table.lock().expect("Lock failed at calculation thread");
        let mut cntarr = vec![vec![0u16; CELL_WIDTH]; CELL_HEIGHT];
        //Count neighbors of all cells
        for x in 0..CELL_WIDTH {
            for y in 0..CELL_HEIGHT {
                //If cell is alive add to all surounding cells
                if arr[x][y] {
                    for xm in -1..2 as i16 {
                        for ym in -1..2 as i16 {
                            //dont add to self
                            if xm != 0 || ym != 0 {
                                //warp at the start and end of the field
                                let realx = warp(x as i16 + xm, CELL_WIDTH, 0);
                                let realy = warp(y as i16 + ym, CELL_HEIGHT, 0);
                                cntarr[realx][realy] += 1;
                            }
                        }
                    }
                }
            }
        }

        for x in 0..CELL_WIDTH {
            for y in 0..CELL_HEIGHT {
                let n = cntarr[x][y];
                //Evaluate Rules(Conway)
                if arr[x][y] {
                    if n > CELL_UPPER_LIMIT {
                        arr[x][y] = false;
                    } else if n < CELL_LOWER_LIMIT {
                        arr[x][y] = false;
                    }
                } else {
                    if n == CELL_BE_REBORN {
                        arr[x][y] = true;
                    }
                }
            }
        }
        //Allways free Vector-Lock before repaint call!
        drop(arr);
        s.send(GuiCommand::Repaint);
    });
}
